package com.pinfly.common.util;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.activation.DataHandler;
import javax.activation.FileDataSource;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;

import org.apache.log4j.Logger;

import com.pinfly.common.config.ConfigException;
import com.pinfly.common.config.PfyPropertiesFactory;

/**
 * A class that provides utility functions for sending e-mail.
 * 
 */
public class Mail
{
    private static final Logger logger = Logger.getLogger (Mail.class);
    public static final String DEFAULT_ENCODE = "UTF-8";
    public static final String TEXT_CONTENT_TYPE = "text/plain;charset=" + DEFAULT_ENCODE;
    public static final String HTML_CONTENT_TYPE = "text/html;charset=" + DEFAULT_ENCODE;
    public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";

    private static final String POST_MASTER = "mail.postmaster";
    private static final String PASSWORD = "mail.password";

    // Properties file that specifies the mail host
    private static Properties props = null;
    private static Authenticator authenticator = null;
    private static String postmaster = null;

    // All methods are static. Mail should never be instantiated
    private Mail ()
    {
    }

    static
    {
        try
        {
            props = PfyPropertiesFactory.getProperties ("email");
            postmaster = props.getProperty (POST_MASTER, "");
            final String password = props.getProperty (PASSWORD, "");
            authenticator = new javax.mail.Authenticator ()
            {
                protected PasswordAuthentication getPasswordAuthentication ()
                {
                    return new PasswordAuthentication (postmaster, password);
                }
            };
        }
        catch (ConfigException e)
        {
            logger.warn (e.getMessage ());
        }
    }

    /**
     * Send a message to one recipient.
     * 
     * @param fromAddr Internet address of sender
     * @param toAddr Internet address of recipient
     * @param subject Content of subject line
     * @param text Body of message
     */
    public static void sendMessageToOne (InternetAddress toAddr, String subject, String text)
    {
        // Create an array of to addresses with just one entry
        // and let SendMessageToMany do all the work.
        sendMessageToMany (new InternetAddress[]
        { toAddr }, subject, text);
    }

    /**
     * Send a message to one recipient.
     * 
     * @param fromAddr String representation of sender address
     * @param toAddr String representation of recipient
     * @param subject Content of subject line
     * @param text Body of message
     */
    public static void sendMessageToOne (String toAddr, String subject, String text)
    {
        // Convert the string addresses to InternetAddresses and hand off
        // to SendMessageToMany to do the work
        try
        {
            InternetAddress[] toAddrs = new InternetAddress[]
            { new InternetAddress (toAddr) };

            sendMessageToMany (toAddrs, subject, text);

        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + toAddr, ae);
        }
        catch (NullPointerException ex)
        {
            logger.fatal ("Error sending mail, probably null address", ex);
        }
    }

    /**
     * Send a message to many recipients.
     * 
     * @param fromAddr Internet address of sender
     * @param toAddrs Array of internet address of recipients
     * @param subject Content of subject line
     * @param text Body of message
     */
    public static void sendMessageToMany (InternetAddress[] toAddrs, String subject, String text)
    {
        // set up the session
        Session session = Session.getDefaultInstance (props, authenticator);

        try
        {
            // create the message
            Message msg = new MimeMessage (session);

            msg.setFrom (new InternetAddress (postmaster));
            msg.setRecipients (Message.RecipientType.TO, toAddrs);
            msg.setSubject (subject);
            msg.setSentDate (new Date ());
            msg.setText (text);

            // send the message
            Transport.send (msg);

        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + postmaster, ae);
        }
        catch (MessagingException mex)
        {
            logger.fatal ("Exception caught in SendMessageToMany", mex);
        }
    }

    /**
     * Sends an email with an attachment.
     * 
     * @param fromAddr String representation of sender address
     * @param toAddr array of String representation of recipient addresses
     * @param subject Content of subject line
     * @param text Body of message
     * @param fileName name of file to attach to message
     */
    public static void sendMessageWithAttachedFile (String[] toAddr, String subject, String text, String fileName)
    {
        // Convert the string addresses to InternetAddresses and hand off
        // to sendMessageWithAttachedFile to do the work
        try
        {
            InternetAddress[] toAddrs = new InternetAddress[toAddr.length];
            for (int x = 0; x < toAddr.length; x++)
            {
                toAddrs[x] = new InternetAddress (toAddr[x]);
            }

            sendMessageWithAttachedFile (toAddrs, subject, text, fileName);
        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + toAddr, ae);
        }
        catch (NullPointerException ex)
        {
            logger.fatal ("Error sending mail, probably null address", ex);
        }
    }

    /**
     * Sends an email with an attachment.
     * 
     * @param fromAddr String representation of sender address
     * @param toAddr String representation of recipient address
     * @param subject Content of subject line
     * @param text Body of message
     * @param fileName name of file to attach to message
     */
    public static void sendMessageWithAttachedFile (String toAddr, String subject, String text, String fileName)
    {
        // Convert the string addresses to InternetAddresses and hand off
        // to sendMessageWithAttachedFile to do the work
        try
        {
            InternetAddress[] toAddrs = new InternetAddress[]
            { new InternetAddress (toAddr) };

            sendMessageWithAttachedFile (toAddrs, subject, text, fileName);
        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + toAddr, ae);
        }
        catch (NullPointerException ex)
        {
            logger.fatal ("Error sending mail, probably null address", ex);
        }
    }

    /**
     * Sends an email with an attachment.
     * 
     * @param fromAddr internet address of sender address
     * @param toAddrs array of internet address of recipients
     * @param subject Content of subject line
     * @param text Body of message
     * @param fileName name of file to attach to message
     */
    public static void sendMessageWithAttachedFile (InternetAddress[] toAddrs, String subject, String text,
                                                    String fileName)
    {
        try
        {
            // set up the session
            Session session = Session.getDefaultInstance (props, authenticator);

            // create the message
            Message msg = new MimeMessage (session);

            msg.setFrom (new InternetAddress (postmaster));
            msg.setRecipients (Message.RecipientType.TO, toAddrs);
            msg.setSubject (subject);
            msg.setSentDate (new Date ());

            Multipart mp = new MimeMultipart ();
            MimeBodyPart mbp1 = new MimeBodyPart ();
            mbp1.setText (text);
            MimeBodyPart mbp2 = null;
            if (fileName == null)
                mp.addBodyPart (mbp1);
            else
            {
                mbp2 = new MimeBodyPart ();
                FileDataSource fds = new FileDataSource (fileName);
                mbp2.setDataHandler (new DataHandler (fds));
                mbp2.setFileName (fileName);
                mp.addBodyPart (mbp1);
                mp.addBodyPart (mbp2);
            }

            // add the Multipart to the message
            msg.setContent (mp);

            // send the message
            Transport.send (msg);
        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + postmaster, ae);
        }
        catch (MessagingException mex)
        {
            logger.fatal ("Exception caught in sendMessageWithAttachedFile", mex);
        }
    }

    public static void sendMessage (String[] toAddr, String subject, String text, String fileName)
    {
        try
        {
            InternetAddress[] toAddrs = new InternetAddress[toAddr.length];
            for (int x = 0; x < toAddr.length; x++)
            {
                toAddrs[x] = new InternetAddress (toAddr[x]);
            }

            // set up the session
            Session session = Session.getDefaultInstance (props, authenticator);

            // create the message
            Message msg = new MimeMessage (session);

            msg.setFrom (new InternetAddress (postmaster));
            msg.setRecipients (Message.RecipientType.TO, toAddrs);
            msg.setSubject (MimeUtility.encodeText (subject, "utf-8", "B"));
            msg.setSentDate (new Date ());
            // 设置优先级(1:紧急 3:普通 5:低)
            msg.addHeader ("X-Priority", "5");

            Multipart mp = new MimeMultipart ();
            MimeBodyPart mbp1 = new MimeBodyPart ();
            mbp1.setHeader (CONTENT_TRANSFER_ENCODING, "base64");
            if (text.equals (htmlToText (text)))
            {
                mbp1.setContent (text, TEXT_CONTENT_TYPE);
            }
            else
            {
                mbp1.setContent (text, HTML_CONTENT_TYPE);
            }

            MimeBodyPart mbp2 = null;
            if (fileName == null)
                mp.addBodyPart (mbp1);
            else
            {
                mbp2 = new MimeBodyPart ();
                FileDataSource fds = new FileDataSource (fileName);
                mbp2.setDataHandler (new DataHandler (fds));
                mbp2.setFileName (fileName);
                mbp2.setHeader (CONTENT_TRANSFER_ENCODING, "base64");
                mp.addBodyPart (mbp1);
                mp.addBodyPart (mbp2);
            }

            // add the Multipart to the message
            msg.setContent (mp);

            // send the message
            Transport.send (msg);
        }
        catch (AddressException ae)
        {
            logger.fatal ("Invalid address: " + postmaster, ae);
        }
        catch (MessagingException mex)
        {
            logger.fatal ("Exception caught in sendMessageWithAttachedFile", mex);
        }
        catch (UnsupportedEncodingException e)
        {
            logger.warn (e.getMessage ());
        }
    }

    /**
     * Validate an e-mail address
     * 
     * An valid e-mail address is a string that has exactly one '@' character,
     * and the '@' is not at the beginning or end of the string
     * 
     * @param addr The address to validate
     * @return true if address is valid; false if address is invalid
     */
    public static boolean validEmailAddress (String addr)
    {
        // check for an empty string
        if (addr == null || addr.length () == 0)
        {
            return false;
        }

        // check on the @ char
        int at = addr.indexOf ('@');
        if (at == -1 || at == 0 || at == (addr.length () - 1))
        {
            return false;
        }

        // make sure there is only one @ char
        if (at != addr.lastIndexOf ('@'))
        {
            return false;
        }

        // If get here, must be okay
        return true;
    }

    /**
     * Check the email address is valid or not.
     * 
     * @param emailAddress
     * @return
     */
    public static boolean isAnEmailAddress (String emailAddress)
    {
        if (emailAddress == null)
        {
            return false;
        }
        String regex = "^[A-Za-z0-9]+((_|\\.|\\-)?[A-Za-z0-9]+)*@([A-Za-z0-9]+\\.)+[A-Za-z0-9]+$";
        return emailAddress.trim ().matches (regex);
    }

    /**
     * Replace all HTML characters
     * 
     * @param s
     * @return
     */
    public static String htmlToText (String s)
    {
        if (s == null || s.trim ().length () == 0)
        {
            return "";
        }
        s = s.replaceAll ("\n", " ");
        s = s.replaceAll ("(?si)<br\\s*?/?>", "\n");
        s = s.replaceAll ("(?si)</div\\s*?>", "\n");
        s = s.replaceAll ("(\\w)<.*?>(\\w)", "$1 $2");
        s = s.replaceAll ("<.*?>", "");
        s = s.replaceAll ("[ \t]+", " ");
        return s;
    }

}